<script setup>
import NProgress from '@/plugins/nprogress'
import * as THREE from 'three'
import { PerspectiveCamera, Raycaster, Scene, Vector2, WebGLRenderer } from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import { onMounted, onUnmounted, ref, toRefs } from 'vue'
import { isDEV, MODEL_PATH, PUBLIC_PATH, startFrom, startTo } from './threejsUtil/config.js'
import { roamView, roamViewSequence } from '@/views/HomePage/modelCanvas/threejsUtil/roamView.js'
import { loadEnv, addLight, renderControls } from './threejsUtil/utils.js'
import { config, viewConfig } from './modelConfig/config.js'
import { addImageSprite } from './threejsUtil/sprite.js'
// 移除场景中全部模型 留下灯光
import gsap from 'gsap'

const clock = new THREE.Clock()
const loadPercent = ref(0)
const canvas = ref(null)
const wrapperDom = ref(null) // 容器dom
const spinning = ref(true)
/*
 * 定义鼠标坐标
 * */
let beforeX
let beforeY
let afterX
let afterY
/*
 * 定义场景
 * */
let scene // 场景
let camera // 相机
let renderer // 渲染器
let controls // 控制器
let requestAnimationFrameTimer // 渲染循环
let gltfLoader // gltf加载器
let gltfScene // gltf场景
/* EDN*/
/*射线模型*/
let rayCasterModels = []
/* 获取canvas的宽高*/
const props = defineProps({
  width: {
    type: Number,
    default: 0
  },
  height: {
    type: Number,
    default: 0
  }
})
const { width, height } = toRefs(props)
const getWidthAndHeight = () => {
  return {
    width: width.value || wrapperDom.value.clientWidth,
    height: height.value || wrapperDom.value.clientHeight
  }
}
/*END*/
/*初始化*/
const init = async () => {
  const { width, height } = getWidthAndHeight()
  scene = new Scene()
  camera = new PerspectiveCamera(45, width / height, 10, 10000)
  camera.position.copy({
    x: startTo.px,
    y: startTo.py,
    z: startTo.pz
  })
  renderer = new WebGLRenderer({
    canvas: canvas.value,
    antialias: true,
    alpha: true,
    powerPreference: 'high-performance',
    precision: 'mediump' // 对于大多数场景足够   可以提高性能 但是会损失精度
  })
  renderer.setSize(width, height, false)
  controls = new OrbitControls(camera, renderer.domElement)
  controls.target.copy({
    x: startTo.tx,
    y: startTo.ty,
    z: startTo.tz
  })
  controls.update()
  // 设置放大缩放的范围
  controls.minDistance = 10 // 设置最小距离
  controls.maxDistance = 2000 // 设置最大距离
  // 平移范围限制
  controls.minPolarAngle = Math.PI / 4 // 最小仰角
  controls.maxPolarAngle = Math.PI / 2 // 最大仰角
  controls.enableDamping = false // 禁用惯性
  const gltf = await loadGltf(`daYuShuiChang.draco.glb`)
  gltfScene = gltf.scene
  scene.add(gltfScene)
  addLight(scene, renderer) //添加灯光
  requestAnimationFrameTimer = requestAnimationFrame(render) //   渲染循环
  // 漫游
  spinning.value = false
  roamView({
    controls,
    from: startFrom,
    to: startTo,
    duration: 2.3
  })
  await loadIconSprite()
}
/*EDN*/
/* 加载模型*/
const loadGltf = (modelName) => {
  if (!gltfLoader) {
    gltfLoader = new GLTFLoader()
    const dracoLoader = new DRACOLoader()
    dracoLoader.setDecoderPath(`${PUBLIC_PATH}draco/`)
    gltfLoader.setDRACOLoader(dracoLoader)
  }
  return new Promise((resolve, reject) => {
    gltfLoader.load(
      `${MODEL_PATH}${modelName}`,
      (gltf) => resolve(gltf),
      ({ loaded, total }) => {
        loadPercent.value = Math.floor((loaded / total) * 100)
        NProgress.inc(loaded / total)
        if (loaded === total) {
          NProgress.done(true)
          loadPercent.value = '文件下载完成，解析中...'
          console.log('文件下载完成，解析中...')
        }
      },
      reject
    )
  })
}
/*加载ICONsprite*/
const loadIconSprite = async () => {
  for (const item of config) {
    const { sprite } = await addImageSprite(scene, item.iconId, '', item.imgUrl)
    if (sprite) {
      sprite.userData.name = item.name
      sprite.userData.idName = item.idName
      rayCasterModels.push(sprite)
    }
  }
}

// 重置相机
function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement
  const width = canvas.clientWidth
  const height = canvas.clientHeight
  const needResize = canvas.width !== width || canvas.height !== height
  if (needResize) {
    renderer.setSize(width, height, false)
  }
  return needResize
}

/*
 * 鼠标事件
 * */
// 鼠标移入后显示的名称
const raycaster = new Raycaster() // 射线
const mousePos = new Vector2() // 鼠标位置
/*鼠标移动*/
const onMouseMove = (e) => {
  const { top, left } = wrapperDom.value?.getBoundingClientRect()
  if (wrapperDom.value) {
    mousePos.x = ((e.clientX - left) / wrapperDom.value?.clientWidth) * 2 - 1
    mousePos.y = -((e.clientY - top) / wrapperDom.value?.clientHeight) * 2 + 1
  }
  raycaster.setFromCamera(mousePos, camera)
  const intersects = raycaster.intersectObjects(rayCasterModels, true)
  const object = intersects[0]?.object
  document.body.style.cursor = 'default'
  if (!object) return
  // 鼠标移入后变小手
  document.body.style.cursor = 'pointer'
}
const onMouseDown = (e) => {
  ;[beforeX, beforeY] = [e.offsetX, e.offsetY]
}
const onMouseUp = (e) => {
  ;[afterX, afterY] = [e.offsetX, e.offsetY]
}
const onMouseClick = (event) => {
  mousePos.x = (event.offsetX / wrapperDom.value?.clientWidth) * 2 - 1
  mousePos.y = -(event.offsetY / wrapperDom.value?.clientHeight) * 2 + 1
  // 拖动鼠标时，不触发点击事件
  if (!(beforeX === afterX && beforeY === afterY)) {
    return
  }
  raycaster.setFromCamera(mousePos, camera)
  const intersects = raycaster.intersectObjects(rayCasterModels, true)
  const object = intersects[0]?.object
  /*如果visible = true*/
  if ((object && !object.parent.visible) || !object?.visible) {
    return
  }
  if (!object) return
  object.visible = false
  loadCurrentModel(object.userData.idName, object.userData.name)
}

/*
 * 渲染循环
 * */
let lastTime = 0;
function render(time) {
  raycaster.setFromCamera(mousePos, camera)
  const deltaTime = time - lastTime;
  if (deltaTime < 16) { // 限制帧率不超过 60fps
    requestAnimationFrame(render);
    return;
  }
  lastTime = time;
  renderControls(controls) // 更新 OrbitControls
  requestAnimationFrameTimer = requestAnimationFrame(render)
  renderer.render(scene, camera)
  if (resizeRendererToDisplaySize(renderer)) {
    camera.aspect = wrapperDom.value?.clientWidth / wrapperDom.value?.clientHeight
    camera.updateProjectionMatrix()
  }
}

/** 开发相关 获取 */
const getControlTarAndPos = (print = true) => {
  // back()
  const target = controls.target
  const position = controls.object.position
  const targetS = JSON.stringify(target)
  const positionS = JSON.stringify(position)
  if (print) {
    const getStyle = (color = '#f00') => `padding: 2px 4px; color: #ffffff; background: ${color};`
    console.log(`%ctarget: ${targetS}`, getStyle())
    console.log(`%cposition: ${positionS}`, getStyle('#049F70FF'))
  }
  return { target, position }
}
/*
 *   隐藏掉所有
 * */
const fadeOutAndRemove = async () => {
  //   隐藏所有模型
  scene.children.forEach((child) => {
    if (child.type !== 'AmbientLight') {
      child.children.forEach((item) => {
        item.visible = false
      })
    }
  })
}
/*
 * 隐藏在消失
 * */
//  调整相机const
const adjustCameraToObject = (object, name, distanceMultiplier = 2) => {
  // 检查 viewConfig 配置
  const option = viewConfig[name]
  controlDoor(name)
  if (!option) {
    console.warn('请检查 viewConfig 配置项')
    return
  }
  // 设置最小距离不限制
  controls.minDistance = 0
  // 获取目标物体的包围盒和中心点
  const box = new THREE.Box3().setFromObject(object)
  const center = new THREE.Vector3()
  box.getCenter(center) // 获取模型的中心点
  // 获取目标物体的尺寸
  const size = new THREE.Vector3()
  box.getSize(size) // 获取模型的尺寸
  // 计算相机到模型的距离
  const maxDim = Math.max(size.x, size.y, size.z)
  const cameraDistance = maxDim * distanceMultiplier // 可配置的距离倍数
  // 设置相机的位置
  // 如果需要平滑过渡效果，可以取消以下注释
  if (option.controlState && option.controlState.length > 0) {
    roamViewSequence({
      controls,
      viewpoints: option.controlState,
      duration: 2.3,
      ease: 'linear',
      onComplete: () => {}
    })
  } else if (option.controlState) {
    const target = controls.target
    const position = controls.object.position
    roamView({
      controls,
      from: {
        tx: target.x,
        ty: target.y,
        tz: target.z,
        px: position.x,
        py: position.y,
        pz: position.z
      },
      to: option.controlState,
      duration: 2,
      onComplete: () => {
        console.log('视角过渡完成')
      }
    })
  } else {
    camera.position.copy(center)
    camera.position.z += cameraDistance // 将相机向后移动
    // 设置控制器的目标点
    controls.target.copy(center)
  }

  // 更新控制器
  controls.update()
}
// 进行控制门
const controlDoor = (name) => {
  // 检查 viewConfig 配置
  const option = viewConfig[name]
  if (!option) {
    console.warn('请检查 viewConfig 配置项')
    return
  }
  if (option.doors) {
    const doors = option.doors
    doors.forEach((item) => {
      const door = scene.getObjectByName(item.idName)
      if (door) {
        //   门旋转
        let axis = item.axis || 'y'
        gsap.to(door.rotation, {
          [axis]: item.rotate,
          delay: item.delay,
          duration: item.duration,
          ease: 'none' // 缓动函数
        })
      }
    })
  } else {
    console.warn('请检查 viewConfig 配置项')
  }
}
// 恢复门
const resetDoor = (name) => {
  // 检查 viewConfig 配置
  const option = viewConfig[name]
  if (!option) {
    console.warn('请检查 viewConfig 配置项')
    return
  }
  if (option.doors) {
    const doors = option.doors
    doors.forEach((item) => {
      const door = scene.getObjectByName(item.idName)
      if (door) {
        //   门旋转
        let axis = item.axis || 'y'
        gsap.to(door.rotation, {
          [axis]: 0,
          duration: 2,
          ease: 'none' // 缓动函数
        })
      }
    })
  } else {
    console.warn('请检查 viewConfig 配置项')
  }
}
/** 使墙体变透明 */
const transparentWall = (scene, name, opacity = 0.2) => {
  const wallGroup = config.find((item) => item.name === name)?.wallGroup || []
  wallGroup.forEach((item) => {
    const wallOption = scene.getObjectByName(item)
    if (wallOption && wallOption instanceof THREE.Mesh) {
      // 设置初始透明度
      wallOption.material.transparent = true
      wallOption.material.opacity = 0 // 假设从完全透明开始
      // 使用 GSAP 创建动画
      gsap.to(wallOption.material, {
        opacity: opacity, // 目标透明度
        duration: 1, // 动画持续时间（1秒）
        ease: 'none' // 缓动函数，可以根据需要调整
      })
    }
  })
}
// 加载当前
const loadCurrentModel = async (idName, name) => {
  // 场景中添加 id 为 modelId 的模型
  await fadeOutAndRemove()
  const model = gltfScene.getObjectByName(idName)
  if (model) {
    adjustCameraToObject(model, name) //   调整相机
    model.visible = true
    transparentWall(model, name) // 透明
    camera.near = 0.1 // 设置相机的近裁剪面
    camera.updateProjectionMatrix() // 更新投影矩阵
  } else {
    console.log('模型不存在')
  }
}
// 重置
const reset = () => {
  camera.near = 10 // 设置相机的近裁剪面
  // 设置最小距离不限制
  controls.minDistance = 10
  camera.updateProjectionMatrix() // 更新投影矩阵
  scene.children.forEach((child) => {
    child.children.forEach((childItem) => {
      if (childItem.name.includes('图标')) {
        childItem.children.forEach((item) => {
          if (item.type === 'Sprite') {
            item.visible = true
          }
        })
      }
      childItem.visible = true
      gsap.to(childItem.material, {
        opacity: 1, // 目标透明度
        duration: 1, // 动画持续时间（1秒）
        ease: 'none' // 缓动函数，可以根据需要调整
      })
    })
  })
  const target = controls.target
  const position = controls.object.position
  roamView({
    controls,
    from: {
      tx: target.x,
      ty: target.y,
      tz: target.z,
      px: position.x,
      py: position.y,
      pz: position.z
    },
    to: startTo,
    duration: 2,
    onComplete: () => {
      console.log('视角过渡完成')
    }
  })
  // 使用场景中的透明的模型 都恢复 加上过度
  config.forEach((item) => {
    const wallGroup = item?.wallGroup || []
    resetDoor(item.name)
    const childIconItem = gltfScene.getObjectByName(item.iconId)
    if (childIconItem.name.includes('图标')) {
      childIconItem.children.forEach((item) => {
        if (item.type === 'Sprite') {
          item.visible = true
        }
      })
    }
    wallGroup.forEach((wallName) => {
      const wallOption = scene.getObjectByName(wallName)
      if (wallOption && wallOption instanceof THREE.Mesh) {
        gsap.to(wallOption.material, {
          opacity: 1, // 目标透明度
          duration: 1, // 动画持续时间（1秒）
          ease: 'none' // 缓动函数，可以根据需要调整
        })
      }
    })
  })
  controls.update()
}

// 销毁函数优化
const destroy = () => {
  try {
    // 取消动画帧请求
    cancelAnimationFrame(requestAnimationFrameTimer)

    // 清除所有 gsap 动画
    gsap.globalTimeline.clear()

    // 释放渲染器资源
    renderer.dispose()
    renderer.forceContextLoss()
    renderer.domElement = null

    // 遍历场景释放资源
    scene.traverse((object) => {
      if (object.isMesh) {
        if (object.geometry) {
          object.geometry.dispose()
        }
        if (object.material) {
          if (Array.isArray(object.material)) {
            object.material.forEach((mat) => {
              if (mat.map) mat.map.dispose()
              mat.dispose()
            })
          } else {
            if (object.material.map) object.material.map.dispose()
            object.material.dispose()
          }
        }
      }
    })

    // 销毁控制器
    if (controls) {
      controls.dispose()
    }

    // 销毁加载器
    if (gltfLoader) {
      gltfLoader.manager = null
      gltfLoader = null
    }

    // 重置所有变量
    camera = null
    scene = null
    gltfScene = null
    rayCasterModels = []
  } catch (e) {
    console.error('Free Three Error:', e)
  }
}

// 合并事件监听和移除逻辑
const setupEventListeners = () => {
  if (wrapperDom.value) {
    wrapperDom.value.addEventListener('pointermove', onMouseMove)
    wrapperDom.value.addEventListener('mousedown', onMouseDown, false)
    wrapperDom.value.addEventListener('mouseup', onMouseUp, false)
    wrapperDom.value.addEventListener('click', onMouseClick)
  }
  window.addEventListener('resize', onResize)
}

const removeEventListeners = () => {
  if (wrapperDom.value) {
    wrapperDom.value.removeEventListener('pointermove', onMouseMove)
    wrapperDom.value.removeEventListener('mousedown', onMouseDown, false)
    wrapperDom.value.removeEventListener('mouseup', onMouseUp, false)
    wrapperDom.value.removeEventListener('click', onMouseClick)
  }
  window.removeEventListener('resize', onResize)
}
const onResize = () => {
  const { width, height } = getWidthAndHeight()
  camera.aspect = width / height
  camera.updateProjectionMatrix()
  renderer.setSize(width, height, false)
}
onMounted(async () => {
  await init()
  setupEventListeners()
})
onUnmounted(() => {
  cancelAnimationFrame(requestAnimationFrameTimer)
  destroy()
  removeEventListeners()
})
</script>
<template>
  <div v-if="isDEV" class="getControlTarAndPos">
    <div @click="getControlTarAndPos">获取视角</div>
    <div @click="reset">返回</div>
  </div>
  <div id="wrapper-canvas" ref="wrapperDom" class="wrapper">
    <a-spin :spinning="spinning" :tip="loadPercent" style="width: 100%">
      <canvas ref="canvas" class="three-canvas"></canvas>
    </a-spin>
  </div>
</template>
<style lang="scss" scoped>
.three-canvas {
  width: 100%;
  height: 100%;
  overflow: hidden;
  clear: both;
}

.wrapper {
  width: calc(100vw);
  height: calc(100vh);
  position: absolute;
}

.getControlTarAndPos {
  position: absolute;
  top: 30px;
  z-index: 11;
  right: 30px;
}

.popup-label {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 10;
  padding: 2px 4px;
  pointer-events: none;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.6);
  transform: translate(-50%, -100%);
  font-size: 16px;
  text-align: center;
  backdrop-filter: blur(6px);
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
  user-select: none;
  white-space: nowrap;
}

.build-name {
  position: absolute;
  background: #0ec79b;
  padding: 5px 10px;
  border-radius: 5px;
  cursor: pointer;
  color: #fff;
  transform: translate(-50%, -50%);
}

.ant-spin-spinning {
  justify-content: center;
  align-items: center;
  display: flex;
  height: calc(100vh - 50px);
}
</style>
